package genealogy.deprecated.datamodel.pedigree;

import genealogy.deprecated.datamodel.pedigree.visitors.IPedigreeVisitor;

import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.dftproject.lineagelinkage.utils.IOFactory;



/**
 * @author Stephen Ivie
 */
@Deprecated
public class Individual implements Serializable , IPedigreeVisitable{

	// serialVersionUID
	private static final long serialVersionUID = -2798580407983748846L;

	private Name m_name;

	// Relationship to the parent node of this node (eg root node is self, self
	// has a father, etc).
	private RelationshipType m_relationship;

	// A list of relatives that are only one "step" away, eg. child, sibling,
	// spouce, mother, father
	private List<Individual> m_relations;
			
	//The list of important events of an individual
	private Map<String, LifeEvent> m_events;

	private Gender m_gender;
	
	private void initialize() {
		m_relations = new ArrayList<Individual>();
		m_events = new HashMap<String, LifeEvent>();
		m_gender = Gender.UNKNOWN;
	}

	/**
	 * Constructor
	 * 
	 * @param relationship
	 * @param name
	 */
	private Individual(RelationshipType relationship, String name) {
		super();
		m_relationship = relationship;
		m_name = new Name(name);
		initialize();
	}

	/**
	 * Constructor
	 * 
	 * @param name
	 */
	public Individual(String name) {
		super();
		m_relationship = RelationshipType.BASE;
		m_name = new Name(name);
		initialize();
	}

	/**
	 * Adds an event to this persons life
	 * 
	 * @param eventNameKey the key name of the event (Ex: birth, death, etc.)
	 *            The key must be the same from one database to the next.
	 * @param event A non-null event to add
	 * @throws PedigreeValidationError if the event or key are null, or if the event
	 *             is non unique
	 */
	public void addEvent(String eventNameKey, LifeEvent event)
			throws PedigreeValidationError {
		if (eventNameKey == null || eventNameKey.length() == 0 || event == null)
		{
			throw new PedigreeValidationError("Cannot add a null event");
		}

		eventNameKey = eventNameKey.toUpperCase();
		if (m_events.containsKey(eventNameKey))
		{
			throw new PedigreeValidationError("Cannot add duplicate event: "
					+ eventNameKey);
		}

		m_events.put(eventNameKey, event);
		event.setEventName(eventNameKey);
	}

	/**
	 * Add a father
	 * @param name father'name
	 * @return the individual object representing the father
	 */
	public Individual addFather(String name){
		assert(!hasFather());
		Individual father = new Individual(RelationshipType.FATHER, name);
		m_relations.add(father);
		return father;
	}
	
	/**
	 * Add a mother
	 * @param name mother'name
	 * @return the individual object representing the mother
	 */
	public Individual addMother(String name){
		assert(!hasMother());
		Individual mother = new Individual(RelationshipType.MOTHER, name);
		m_relations.add(mother);
		return mother;
	}
	
	/**
	 * Add a spouse
	 * @param name spouse'name
	 * @return the individual object representing the spouse
	 */
	public Individual addSpouse(String name){
		Individual spouse = new Individual(RelationshipType.SPOUSE, name);
		m_relations.add(spouse);
		return spouse;
	}
	
	/**
	 * Add a sibling
	 * @param name sibling'name
	 * @return the individual object representing the sibling
	 */
	public Individual addSibling(String name){
		Individual sibling = new Individual(RelationshipType.SIBLING, name);
		m_relations.add(sibling);
		return sibling;
	}
	
	/**
	 * Add a child
	 * @param name child'name
	 * @return the individual object representing the child
	 */
	public Individual addChild(String name){
		Individual child = new Individual(RelationshipType.CHILD, name);
		m_relations.add(child);
		return child;
	}
	
	/**
	 * Adds an unknown relation to an individual
	 * @param name
	 * @return the individual object representing the relation
	 */
	public Individual addUnknownRelation(String name){
		Individual unknown = new Individual(RelationshipType.UNKNOWN, name);
		m_relations.add(unknown);
		return unknown;
	}
	
	/**
	 * Adds a preconstructed relation
	 * @param relation a father, mother, sibling child, or spouse of this individual (or unknown)
	 * @throws PedigreeValidationError
	 * @pre relation cannot be null
	 * @post
	 */
	public void addRelation(Individual relation) throws PedigreeValidationError{
		if(relation == null){
			throw new PedigreeValidationError("Cannot add a null individual.");
		}
		m_relations.add(relation);
	}
	
	/**
	 * @return the events
	 */
	public Map<String, LifeEvent> getEvents() {
		return m_events;
	}
	
	public List<String> getEventNames(){
		Set<String> eventNamesSet = m_events.keySet();
		List<String> eventNames = new ArrayList<String>();
		eventNames.addAll(eventNamesSet);
		return eventNames;
	}

	/**
	 * @return the relations
	 */
	public List<Individual> getRelations() {
		return m_relations;
	}

	/**
	 * @return the relationship
	 */
	public RelationshipType getRelationship() {
		return m_relationship;
	}

	/**
	 * @return the name
	 */
	public Name getName() {
		return m_name;
	}

	/**
	 * Getter for the gender
	 * @return the gender
	 * @pre none
	 * @post result is not null
	 */
	public Gender getGender() {
		return m_gender;
	}

	/**
	 * Setter for the gender
	 * @param gender the gender to set
	 * @pre gender != null
	 * @post the gender is set on this instance
	 */
	public void setGender(Gender gender) {
		m_gender = gender;
	}

	/**
	 * Setter for the relationship
	 * @param relationship the relationship to set
	 * @pre relationship != null
	 * @post the relationship is set on this instance
	 */
	public void setRelationship(RelationshipType relationship) {
		m_relationship = relationship;
	}

	/**
	 * 
	 * @param relationship
	 * @pre 
	 * @post
	 */
	public List<Individual> getRelationshipsOfType(RelationshipType relationshipType) {
		List<Individual> relations = new ArrayList<Individual>();
		if(m_relations != null){
			for(Individual relation : m_relations){
				if(relation.getRelationship() == relationshipType){
					relations.add(relation);
				}
			}
		}
		return relations;
	}
	
	public Individual getRelation(RelationshipType relationshipType){
		if(m_relations != null){
			for(Individual aRelation : m_relations){
				if(aRelation.getRelationship() == relationshipType){
					return aRelation;
				}
			}
		}
		return null;
	}
	
	public Individual getFather(){
		return getRelation(RelationshipType.FATHER);
	}
	
	public Individual getMother(){
		return getRelation(RelationshipType.MOTHER);
	}
	
	public List<Individual> getSpouses(){
		return getRelationshipsOfType(RelationshipType.SPOUSE);
	}
	
	public List<Individual> getSiblings(){
		return getRelationshipsOfType(RelationshipType.SIBLING);
	}
	
	public List<Individual> getChildren(){
		return getRelationshipsOfType(RelationshipType.CHILD);
	}
	
	private boolean hasRelation(RelationshipType relation){
		for(Individual individual : m_relations){
			if(individual.m_relationship.equals(relation)){
				return true;
			}
		}
		return false;
	}
	
	public boolean hasFather(){
		return hasRelation(RelationshipType.FATHER);
	}
	
	public boolean hasMother(){
		return hasRelation(RelationshipType.MOTHER);
	}
	
	public boolean hasSpouse(){
		return hasRelation(RelationshipType.SPOUSE);
	}

	public boolean hasSibling(){
		return hasRelation(RelationshipType.SIBLING);
	}
	
	public boolean hasChild(){
		return hasRelation(RelationshipType.CHILD);
	}
	
	public boolean hasUnknownRelation(){
		return hasRelation(RelationshipType.UNKNOWN);
	}
	
	public void toFile(File file) throws IOException, ClassNotFoundException{
		assert (file != null);
        ObjectOutputStream out = null;
        try {
            out = IOFactory.getObjectOutputStream(file);
            out.writeObject(this);
        }
        catch (IOException ex) {
            throw ex;
        }
        finally {
            if (out != null) {
                out.close();
            }
        }
	}
	
	public static Individual fromFile(File file) throws IOException, ClassNotFoundException{
		assert (file != null);
		Individual ind = null;
		ObjectInputStream in = null;
		try {
			in = IOFactory.getObjectInputStream(file);
			ind = (Individual) in.readObject();
        }
        catch (IOException ex) {
            throw ex;
        }
        catch (ClassNotFoundException ex) {
            throw ex;
        }
        finally{
        	if(in != null){
        		in.close();
        	}
        }
        return ind;
	}
	
	
	
	/* (non-Javadoc)
	 * @see genealogy.datamodel.pedigree.IPedigreeVisitable#accept(genealogy.datamodel.pedigree.visitors.AbstractPedigreeVisitor)
	 */
	public void accept(IPedigreeVisitor visitor) {
		visitor.visit(this);
		m_relationship.accept(visitor);
		m_name.accept(visitor);
		m_gender.accept(visitor);
		
		for(LifeEvent event : m_events.values()){
			event.accept(visitor);
		}

		if(m_relations != null){
			for(RelationshipType relationType : RelationshipType.values()){
				List<Individual> relations = getRelationshipsOfType(relationType);
				for(Individual relation : relations){
					relation.accept(visitor);
				}
			}
		}
		visitor.endVisit();
	}
}
